﻿using System;
using System.IO;
using System.Linq;
using Plugins;
using Plugins.Base.Common;
using Plugins.UserControls;
using StockModel;
using TechnicalIndicators.QuotationBased;
using Utils;

namespace MomentumPlugin.Common
{
	internal class DecisionEngine : Plugins.Base.Common.DecisionEngineBase
	{
		#region Attributes

		private readonly HourSelectorData _hourSettings;
		private readonly StrategyControlData _strategySettings;
		private readonly LastValueGenerator _lastValueGenerator;
		private readonly IDecisionEngineExitProcessor _exitProcessor;
		
		#endregion

		#region Properties

		public new DecisionEngineData Data
		{
			get
			{
				return (DecisionEngineData)base.Data;
			}
		}

		#endregion

		#region Public Methods

		public DecisionEngine(DecisionEngineData data, ObservableCollection2<Quotation> quotationSource, StrategyControlData strategySettings, HourSelectorData hourSettings, OperationManager operationManager) : base(data, quotationSource, operationManager)
		{
			_strategySettings = strategySettings;
			_hourSettings = hourSettings;

			_lastValueGenerator = new LastValueGenerator();
			_lastValueGenerator.Configure(true);
			_lastValueGenerator.Source = new ReadWriteLockedObservableCollection<Quotation>(quotationSource, TimeSpan.FromSeconds(10));
			
			_lastValueGenerator.Start();

			base.RegisterForNotifications();

			if (_strategySettings.ExitMode == ExitMode.None)
			{
				throw new InvalidDataException("The exit mode must be configured");
			}
			else
			{
				_exitProcessor = _strategySettings.ExitMode == ExitMode.OnFirstReversal ? new OnFirstReversalProcessor(_hourSettings, this.Data, base.OperationManager, _strategySettings) : new OnFixedAmountProcessor(_hourSettings, this.Data, base.OperationManager, _strategySettings) as IDecisionEngineExitProcessor;	
			}
		}

		protected override void EvaluateStateMachineInternal()
		{
			this.SetState(this.Data.State);
		}

		#endregion

		#region Private Methods

		public void SetState(DecisionEngineStateBase state)
		{
			this.Data.State = state;

			DecisionEngineStateBase newState = null;

			if (state == DecisionEngineState.WaitingOpen)
			{
				newState = this.ProcessWaitingOpen();
			}
			else if (state == DecisionEngineState.WaitingDirection)
			{
				newState = this.ProcessWaitingDirection();
			}
			else if (state == DecisionEngineState.CountingDown)
			{
				newState = this.ProcessCountingDown();
			}
			else if (state == DecisionEngineState.CountingUp)
			{
				newState = this.ProcessCountingUp();
			}
			else if (state == DecisionEngineState.OnLongPossition)
			{
				newState = _exitProcessor.ProcessOnLongPossition();
			}
			else if (state == DecisionEngineState.OnShortPossition)
			{
				newState = _exitProcessor.ProcessOnShortPossition();
			}
			else if (state == DecisionEngineState.DayClosed)
			{
				newState = this.ProcessDayClosed();
			}

			if (newState != null)
			{
				if ((newState == DecisionEngineState.OnLongPossition && this.Data.State != DecisionEngineState.OnLongPossition) || (newState == DecisionEngineState.OnShortPossition && this.Data.State != DecisionEngineState.OnShortPossition))
				{
					this.Data.State = newState; // Do not evaluate these steps after creating them. If the gap is too big we can enter an endless loop between them.
				}
				else
				{
					this.SetState(newState);	
				}
			}
		}

		private DecisionEngineStateBase ProcessWaitingOpen()
		{
			DecisionEngineStateBase result = null;

			if (this.Data.LastQuotation.Date.TimeOfDay > _hourSettings.LastOpen)
			{
				result = DecisionEngineState.DayClosed;
			}
			else if (this.Data.LastQuotation.Volume > 0)
			{
				result = DecisionEngineState.WaitingDirection;
			}

			return result;
		}

		private DecisionEngineStateBase ProcessWaitingDirection()
		{
			DecisionEngineStateBase result = null;

			if (this.Data.LastQuotation.Date.TimeOfDay > _hourSettings.LastOpen)
			{
				result = DecisionEngineState.DayClosed;
			}
			else if (this.Data.PreviousQuotation != null && this.Data.PreviousQuotation.Volume > 0)
			{
				if (this.Data.PreviousQuotation.Value > this.Data.LastQuotation.Value)
				{
					result = DecisionEngineState.CountingDown;
				}
				else if (this.Data.PreviousQuotation.Value < this.Data.LastQuotation.Value)
				{
					result = DecisionEngineState.CountingUp;
				}
			}

			return result;
		}

		private DecisionEngineStateBase ProcessCountingDown()
		{
			DecisionEngineStateBase result = null;

			if (this.Data.LastQuotation.Date.TimeOfDay > _hourSettings.LastOpen)
			{
				result = DecisionEngineState.DayClosed;
			}
			else if (this.Data.PreviousQuotation.Value < this.Data.LastQuotation.Value)
			{
				this.Data.Values.Clear();
				this.Data.Values.Add(this.Data.PreviousQuotation.Value);
				result = DecisionEngineState.CountingUp;
			}
			else
			{
				this.Data.Values.Add(this.Data.LastQuotation.Value);
				this.Data.AccumulatedValue = this.Data.Values.Count > _strategySettings.MaxSeconds + 1 ? this.Data.Values[this.Data.Values.Count - _strategySettings.MaxSeconds - 1] - this.Data.Values.Last() : this.Data.Values.First() - this.Data.Values.Last();
				if (this.Data.AccumulatedValue >= _strategySettings.MinVariation)
				{
					if (this.Data.LastQuotation.Buyers[0].Quantity >= _strategySettings.Volume)
					{
						base.OperationManager.OpenShortPossition(_strategySettings.Volume);
						result = DecisionEngineState.OnShortPossition;
					}
				}
			}

			return result;
		}

		private DecisionEngineStateBase ProcessCountingUp()
		{
			DecisionEngineStateBase result = null;

			if (this.Data.LastQuotation.Date.TimeOfDay > _hourSettings.LastOpen)
			{
				result = DecisionEngineState.DayClosed;
			}
			else if (this.Data.PreviousQuotation.Value > this.Data.LastQuotation.Value)
			{
				this.Data.Values.Clear();
				this.Data.Values.Add(this.Data.PreviousQuotation.Value);
				result = DecisionEngineState.CountingDown;
			}
			else
			{
				this.Data.Values.Add(this.Data.LastQuotation.Value);
				this.Data.AccumulatedValue = this.Data.Values.Count > _strategySettings.MaxSeconds + 1 ? this.Data.Values.Last() - this.Data.Values[this.Data.Values.Count - _strategySettings.MaxSeconds - 1] : this.Data.Values.Last() - this.Data.Values.First();
				if (this.Data.AccumulatedValue >= _strategySettings.MinVariation)
				{
					if (this.Data.LastQuotation.Sellers[0].Quantity >= _strategySettings.Volume)
					{
						base.OperationManager.OpenLongPossition(_strategySettings.Volume);
						result = DecisionEngineState.OnLongPossition;
					}
				}
			}

			return result;
		}

		private DecisionEngineStateBase ProcessDayClosed()
		{
			DecisionEngineStateBase result = null;

			if (this.Data.LastQuotation.Date.TimeOfDay < _hourSettings.LastOpen && (this.Data.LastQuotation.Date.Date != this.Data.PreviousQuotation.Date.Date || this.Data.LastQuotation.Date < this.Data.PreviousQuotation.Date))
			{
				this.Data.Values.Clear();
				
				this.Data.PreviousQuotation = null;

				_lastValueGenerator.Clear();
				_lastValueGenerator.Start();

				result = DecisionEngineState.WaitingOpen;
			}

			return result;
		}

		#endregion

	}
}
